﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices.AccountManagement;
using MTO.Framework.DirectoryServices.AccountManagement.Interfaces;
using MTO.Framework.DirectoryServices.AccountManagement.Enums;

namespace MTO.Framework.DirectoryServices.AccountManagement.LDAP
{
    /// <summary>
    /// Définie la logique pour la gestion des objets dans les Directory Servies.
    /// </summary>
    public abstract class LDAPAccountManager : IAccountManager
    {
        #region Constructeurs

        public LDAPAccountManager(string serverName, string container, string creationContainer, ContextType contextType, ContextOptions contextOptions)
        {
            this.CreationContainer = creationContainer;
            this.ServerName = serverName;
            this.Container = container;
            this.ContextType = contextType;
            this.ContextOptions = contextOptions;
            this.Context = new PrincipalContext(contextType, serverName, container, contextOptions);
        }

        public LDAPAccountManager(string serverName, string container, string creationContainer, ContextType contextType, ContextOptions contextOptions, string userName, string password)
            : this (serverName, container, creationContainer, contextType, contextOptions)
        {
            this.Context = new PrincipalContext(contextType, serverName, container, contextOptions, userName, password);
        }

        #endregion

        #region Properies

        public PrincipalContext Context
        {
            get;
            private set;
        }

        public string CreationContainer
        {
            get;
            private set;
        }

        private string ServerName
        {
            get;
            set;
        }

        private string UserName
        {
            get;
            set;
        }

        private string Password
        {
            get;
            set;
        }

        private string Container
        {
            get;
            set;
        }

        private ContextType ContextType
        {
            get;
            set;
        }

        private ContextOptions ContextOptions
        {
            get;
            set;
        }

        #endregion

        #region Methods

        public bool CreateUser(IUserAccount user, string password)
        {
            if (string.IsNullOrEmpty(this.CreationContainer))
            {
                throw new ArgumentNullException("You must specify a Creation Container to be able to create users");
            }

            if (user.TypeUserAccount == TypesUserAccounts.Interne)
            {
                throw new NotImplementedException();
            }

            if (this.FindUserByIdentity(user.Name) != null)
            {
                throw new InvalidOperationException("L'usager existe déjà");
            }

            var originalContainer = this.Context.Container;

            PrincipalContext creationContext;

            if (!string.IsNullOrEmpty(this.UserName) && !string.IsNullOrEmpty(this.Password))
            {
                creationContext = new PrincipalContext(this.ContextType, this.ServerName, this.CreationContainer, this.ContextOptions, this.UserName, this.Password);
            }
            else
            {
                creationContext = new PrincipalContext(this.ContextType, this.ServerName, this.CreationContainer, this.ContextOptions);
            }

            var userPrincipal = new LDAPUserPrincipal(creationContext, user);
            userPrincipal.UserCannotChangePassword = false;
            userPrincipal.Enabled = true;
            userPrincipal.PasswordNeverExpires = false;
            userPrincipal.PasswordNotRequired = false;
            userPrincipal.SetPassword(password);
            //userPrincipal.ExpirePasswordNow();
            userPrincipal.Save();
            return true;
        }

        public bool ChangePassword(string identity, string oldPassword, string newPassword)
        {
            //if (!this.ValidateCredentials(identity, oldPassword))
            //{
            //    throw new InvalidOperationException("You must specify valid credentials for password changes");
            //}
            var userPrincipal = this.FindUserPrincipalByIdentity(identity);
            if (userPrincipal.TypeUserAccount == TypesUserAccounts.Interne)
            {
                throw new NotImplementedException("Password change cannot be done on userProxy");
            }
            //userPrincipal.SetPassword(newPassword);
            userPrincipal.ChangePassword(oldPassword, newPassword);
            userPrincipal.Save();
            return true;
        }

        public bool SetPassword(string identity, string newPassword)
        {
            var userPrincipal = this.FindUserPrincipalByIdentity(identity);
            if (userPrincipal.TypeUserAccount == TypesUserAccounts.Interne)
            {
                throw new NotImplementedException("Password change cannot be done on userProxy");
            }
            userPrincipal.SetPassword(newPassword);
            userPrincipal.Save();
            return true;
        }

        public bool DeleteUser(IUserAccount user)
        {
            if (user.TypeUserAccount == TypesUserAccounts.Interne)
            {
                throw new NotImplementedException();
            }

            var u = this.FindUserPrincipalByIdentity(user.Name);

            if (u == null)
            {
                throw new InvalidOperationException("L'usager n'existe pas");
            }

            u.Delete();
            return true;
        }

        public bool DeleteUser(string identity)
        {
            var user = this.FindUserPrincipalByIdentity(identity);
            if (user.TypeUserAccount == TypesUserAccounts.Interne)
            {
                throw new NotImplementedException();
            }

            return this.DeleteUser(user);
        }

        public IEnumerable<IUserAccount> GetAllUsers()
        {
            return this.GetAllUsers(new UserAccount());
        }

        public IEnumerable<IUserAccount> GetAllUsers(IUserAccount searchQuery)
        {
           
            IEnumerable<IUserAccount> result = new List<IUserAccount>();
            if (searchQuery.TypeUserAccount == TypesUserAccounts.Tous
                || searchQuery.TypeUserAccount == TypesUserAccounts.Externe)
            {
                var userFilter = new LDAPUserPrincipal(this.Context, searchQuery);
                PrincipalSearcher userSearcher = new PrincipalSearcher(new LDAPUserPrincipal(this.Context));
                if (userFilter != null)
                {
                    userSearcher.QueryFilter = userFilter;
                }

                PrincipalSearchResult<Principal> resultUser = userSearcher.FindAll();
                result = result.Union(resultUser.Cast<LDAPUserPrincipal>());
            }

            if (searchQuery.TypeUserAccount == TypesUserAccounts.Tous
                || searchQuery.TypeUserAccount == TypesUserAccounts.Interne)
            {
                var userProxyFilter = new LDAPUserProxyPrincipal(this.Context, searchQuery);
                PrincipalSearcher userProxySearcher = new PrincipalSearcher(new LDAPUserProxyPrincipal(this.Context));
                if (userProxyFilter != null)
                {
                    userProxySearcher.QueryFilter = userProxyFilter;
                }

                PrincipalSearchResult<Principal> resultUserProxy = userProxySearcher.FindAll();
                result = result.Union(resultUserProxy.Cast<LDAPUserProxyPrincipal>());
            }
            return result;
        }

        public IEnumerable<IUserAccount> GetAllUsers(int pageIndex, int pageSize, out int totalCount)
        {
            return this.GetAllUsers(pageIndex, pageSize, out totalCount, new UserAccount());
        }

        public IEnumerable<IUserAccount> GetAllUsers(int pageIndex, int pageSize, out int totalCount, IUserAccount searchQuery)
        {
            var users = this.GetAllUsers(searchQuery);
            totalCount = users.Count();
            return users.Skip(pageIndex * pageSize).Take(pageSize);
        }

        public IUserAccount GetUser(IUserAccount searchQuery)
        {
            return this.GetUserPrincipal(searchQuery) as IUserAccount;
        }

        public IUserAccount FindUserByIdentity(string identityValue)
        {
            return this.FindUserPrincipalByIdentity(identityValue) as IUserAccount;
        }

        public IUserAccount FindUserBySid(string identitySid)
        {
            return this.FindUserPrincipalByIdentity(identitySid, IdentityType.Sid);
        }

        public IUserAccount FindUserBySamAccountName(string identitySamAccountName)
        {
            return this.FindUserPrincipalByIdentity(identitySamAccountName, IdentityType.SamAccountName);
        }

        public IUserAccount FindUserByUserPrincipalName(string identityUserPrincipalName)
        {
            return this.FindUserPrincipalByIdentity(identityUserPrincipalName, IdentityType.UserPrincipalName);
        }

        public IUserAccount FindUserByName(string identityName)
        {
            return this.FindUserPrincipalByIdentity(identityName, IdentityType.Name);
        }

        private LDAPUserPrincipal GetUserPrincipal(IUserAccount searchQuery)
        {
            Principal resultUserInterne = null;
            Principal resultUserExterne = null;
            var ps = new PrincipalSearcher(new UserPrincipal(this.Context));
            if (searchQuery.TypeUserAccount == TypesUserAccounts.Tous
                || searchQuery.TypeUserAccount == TypesUserAccounts.Interne)
            {
                var user = new LDAPUserProxyPrincipal(this.Context, searchQuery);
                ps.QueryFilter = user;
                resultUserInterne = ps.FindOne();
            }


            if (searchQuery.TypeUserAccount == TypesUserAccounts.Tous
                || searchQuery.TypeUserAccount == TypesUserAccounts.Externe)
            {
                var user = new LDAPUserPrincipal(this.Context, searchQuery);
                ps.QueryFilter = user;
                resultUserExterne = ps.FindOne();
            }

            

            if (resultUserExterne != null && resultUserInterne != null)
            {
                throw new Exception("Deux usagers on été trouvé.");
            }

            var result = resultUserExterne ?? resultUserInterne;

            return result as LDAPUserPrincipal;
        }

        private LDAPUserPrincipal FindUserPrincipalByIdentity(string identityValue)
        {
            var user = LDAPUserPrincipal.FindByIdentity(this.Context, identityValue);
            if (user == null)
            {
                user = LDAPUserProxyPrincipal.FindByIdentity(this.Context, identityValue);
            }

            return user as LDAPUserPrincipal;
        }

        private LDAPUserPrincipal FindUserPrincipalByIdentity(string identityValue, IdentityType identityType)
        {
            var user = LDAPUserPrincipal.FindByIdentity(this.Context, identityType, identityValue);
            if (user == null)
            {
                user = LDAPUserPrincipal.FindByIdentity(this.Context, identityType, identityValue);
            }
            return user;
        }

        public bool UpdateUser(IUserAccount user)
        {
            if (user.TypeUserAccount == TypesUserAccounts.Interne)
            {
                throw new NotImplementedException();
            }

            if (string.IsNullOrEmpty(user.ObjectSid))
            {
                throw new ArgumentException("L'objectSid doit être spécifier");
            }

            var u = this.FindUserPrincipalByIdentity(user.ObjectSid, IdentityType.Sid);
            if (u == null)
            {
                throw new ArgumentException("Aucun utilisateur ne correspond à l'ObjectSid spécifié");
            }

            if (u.TypeUserAccount == TypesUserAccounts.Interne)
            {
                throw new NotImplementedException();
            }

            u.Country = user.Country;
            u.Department = user.Department;
            u.Description = user.Description;
            u.DisplayName = user.DisplayName;
            u.GivenName = user.GivenName;
            u.HomeDirectory = user.HomeDirectory;
            u.Mail = user.Mail;
            u.MailNickname = user.MailNickname;
            //u.Name = user.Name;
            u.SamAccountName = user.SamAccountName;

            u.Save();
            return true;
        }

        public bool ValidateCredentials(string userName, string password)
        {
            return this.Context.ValidateCredentials(userName, password);
        }

        #endregion
    }
}
